package com.only4play.practice.pay.reconcile.core.taskapi;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.only4play.codegen.support.constants.ValidStatus;
import com.only4play.practice.common.enums.HandlerType;
import com.only4play.practice.common.enums.OrderState;
import com.only4play.practice.common.enums.pay.ReconcileMistakeType;
import com.only4play.practice.common.enums.pay.TaskBatchStatusEnum;
import com.only4play.practice.common.enums.pay.TradeStatusEnum;
import com.only4play.practice.common.utils.DateFormatUtils;
import com.only4play.practice.pay.reconcile.core.entity.*;
import com.only4play.practice.pay.reconcile.core.model.payconfig.AliPayConfig;
import com.only4play.practice.pay.reconcile.core.model.payconfig.PayConfig;
import com.only4play.practice.pay.reconcile.core.model.payconfig.PayConfigFactory;
import com.only4play.practice.pay.reconcile.core.model.payconfig.WechatPayConfig;
import com.only4play.practice.pay.reconcile.core.service.*;
import com.only4play.practice.pay.reconcile.core.strategy.download.AliParamNameConstant;
import com.only4play.practice.pay.reconcile.core.strategy.download.AliPayBillFileDownStrategy;
import com.only4play.practice.pay.reconcile.core.strategy.download.WeChatParamNameConstant;
import com.only4play.practice.pay.reconcile.core.strategy.download.WeiXinBillFileDownStrategy;
import com.only4play.practice.pay.reconcile.core.vo.ChannelReconTradeVo;
import com.only4play.practice.pay.reconcile.core.vo.ReconcileParserResultVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.ParseException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author mdy
 * * @date 2024/9/23
 */
@RestController
@RequestMapping("cron/task/reconcile/v1")
@Slf4j
public class ReconcileTaskApi {

    @Resource
    private IMerchantAppService merchantAppService;
    @Resource
    private IReconcileTaskBatchService reconcileTaskBatchService;
    @Resource
    private WeiXinBillFileDownStrategy wechatBillFileDownStrategy;
    @Resource
    private AliPayBillFileDownStrategy aliPayBillFileDownStrategy;

    @Resource
    private IReconcileMistakeScratchPoolService reconcileMistakeScratchPoolService;

    @Resource
    private IReconcileMistakeService reconcileMistakeService;

    @Resource
    private IPlatformOrderService platformOrderService;


    /**
     * TODO 调用的子方法内使用了try catch，则需要把try catch放到子方法中，否则子方法执行异常，父方法不会捕获到异常
     * 可以在子方法捕获到异常时继续向父方法抛出异常，但是这样子方法抛出异常后不会往下执行
     */
    @RequestMapping(value = "/online",method = {RequestMethod.GET})
    public void onlineReconcileTask(@RequestParam(value = "billDateStr",required = false) String billDateStr)  {
        log.info("[onlineReconcileTask] 开始执行，当前时间 = {} ", DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
        // TODO 获取到所有开启了对账的商户配置
        List<MerchantApp> merchantAppEnableList = merchantAppService.findByEnableReconStatus(ValidStatus.VALID.getCode());
        merchantAppEnableList.forEach(merchantApp -> {
            ReconcileTaskBatch reconcileTaskBatch = null;
            // 账单日期可以考虑从配置中拉取对账周期，可以比第三方渠道晚一点，但不能比第三方渠道生成账单时间更早
            // 默认跟微信支付宝对齐 取 T + 1
            // 获取需要对账的对账单时间
            Date billDate = null;
            if (billDateStr == null || billDateStr.isEmpty()){
                billDate = DateFormatUtils.addDaysByCalendar(new Date(),
                        - merchantApp.getReconcileCycle());
            }else {
                billDate = DateFormatUtils.parse(billDateStr, DateFormatUtils.PATTERN_CLASSICAL_SIMPLE);
            }

            // 生成对账任务批次号
            String batchNo =
                    merchantApp.getMchNo() + "-"
                            + DateFormatUtils.format(billDate, DateFormatUtils.PATTERN_CLASSICAL_YMD) + "-"
                            + merchantApp.getChannelType().getCode()
                    ;

            log.info("[onlineReconcileTask] 商户号: {}, 通道: {} 执行对账任务批次 = {}", merchantApp.getMchNo(), merchantApp.getChannelType().getName(),batchNo);

            // 判断是否重复对账
            List<ReconcileTaskBatch> checkedList = reconcileTaskBatchService.checkedIsRepeatReconcile(batchNo);
            if (CollUtil.isNotEmpty(checkedList)){
                log.info("[onlineReconcileTask] 对账任务批次号: {} 已经存在，本次对账任务不执行", batchNo);
                return;
            }
            reconcileTaskBatch = new ReconcileTaskBatch().initEntity();
            reconcileTaskBatch.setTaskBatchNo(batchNo);
            reconcileTaskBatch.setBillDate(billDate);
            reconcileTaskBatch.setMchNo(merchantApp.getMchNo());
            reconcileTaskBatch.setMchName(merchantApp.getMchName());
            reconcileTaskBatch.setChannelName(merchantApp.getChannelType().getName());
            reconcileTaskBatch.setChannelType(merchantApp.getChannelType());

            this.reconcileTaskRun(merchantApp,reconcileTaskBatch);
        });
        if (CollUtil.isEmpty(merchantAppEnableList)){
            log.info("[onlineReconcileTask] merchantAppEnableList 为空，没有开启对账任务的商家");
        }
    }

    private void reconcileTaskRun(MerchantApp merchantApp,ReconcileTaskBatch reconcileTaskBatch) {

        Date billDate = reconcileTaskBatch.getBillDate();
        // 下载并解析对账文件，将各渠道对账单数据转化为统一对账数据类型
        ReconcileParserResultVo reconcileParserResultVo = new ReconcileParserResultVo();
        reconcileParserResultVo.setReconcileTaskBatch(reconcileTaskBatch);
        try {
            PayConfig payConfig = PayConfigFactory.parseConfig(merchantApp.getChannelPayConfig(), merchantApp.getChannelType());
            reconcileTaskBatch.setDownloadStartTime(new Date());
            reconcileParserResultVo = downAndParserData(billDate, payConfig, reconcileParserResultVo);
            reconcileTaskBatch.setDownloadEndTime(new Date());
        }catch (Exception e) {
            log.error("[onlineReconcileTask] 下载并解析对账文件异常:", e);
            reconcileTaskBatch.setTaskBatchStatus(TaskBatchStatusEnum.PARSER_FILE_FAIL);
            reconcileTaskBatchService.save(reconcileTaskBatch);
            return;
        }
        // 具体对账流程
        try {
            reconcileTaskBatch.setReconcileStartTime(new Date());
            check(reconcileParserResultVo);
            reconcileTaskBatch.setReconcileEndTime(new Date());
        } catch (Exception e) {
            log.error("[reconciliationHandler] 对账任务执行异常:", e);
            reconcileTaskBatch.setTaskBatchStatus(TaskBatchStatusEnum.FAIL);
            reconcileTaskBatchService.save(reconcileTaskBatch);
            return;
        }
        // 保存数据
        saveData(reconcileParserResultVo);
        // 如果缓冲池中有三天前的数据就清理掉并记录差错
        validateScratchPool();
    }

    /**
     * 为了保证事务一致性，把对数据库的操作放入一个事务中
     */
    // @Transactional
    public void saveData(ReconcileParserResultVo reconcileParserResultVo) {

        log.info("========>  对完数据对比后业务数据保存处理开始=========>");

        List<ReconcileMistake> mistakeList = reconcileParserResultVo.getMistakeList();
        log.info("===> step2:保存差错记录====总共[" + mistakeList.size() + "]条");
        reconcileMistakeService.saveBatch(mistakeList);

        List<ReconcileMistakeScratchPool> insertScreatchRecordList = reconcileParserResultVo.getInsertScreatchRecordList();
        log.info("===> step3:保存记录到缓存池中====总共[" + insertScreatchRecordList.size() + "]条");
        reconcileMistakeScratchPoolService.saveBatch(insertScreatchRecordList);

        List<ReconcileMistakeScratchPool> removeScreatchRecordList = reconcileParserResultVo.getRemoveScreatchRecordList();
        log.info("===> step4:从缓存池中删除已匹配记录====总共[" + removeScreatchRecordList.size() + "]条");
        reconcileMistakeScratchPoolService.deleteFromPool(removeScreatchRecordList);

        ReconcileTaskBatch reconcileTaskBatch = reconcileParserResultVo.getReconcileTaskBatch();
        log.info("===> step5:保存批次记录====");
        if (!ObjectUtil.equals(reconcileTaskBatch.getTaskBatchStatus(), TaskBatchStatusEnum.NO_BILL)){
            reconcileTaskBatch.setTaskBatchStatus(TaskBatchStatusEnum.SUCCESS);
        }
        reconcileTaskBatch.setTaskEndTime(new Date());
        reconcileTaskBatchService.save(reconcileTaskBatch);
        log.info("<========  对完数据后业务数据处理结束<=========");
    }

    private ReconcileParserResultVo downAndParserData(Date billDate, PayConfig payConfig, ReconcileParserResultVo reconcileParserResultVo) {

        // 下载对账文件并转化为输入流
        InputStream inputStream = null;
        log.info("[onlineReconcileTask] 开始下载对账文件 = {}",DateFormatUtils.format(new Date()));
        inputStream = getBillFileInputStream(billDate, payConfig);
        log.info("[onlineReconcileTask] 开始解析对账文件输入流 = {}",DateFormatUtils.format(new Date()));
        ReconcileParserResultVo parserResult
                = parserBillFileInputStream(inputStream, billDate, reconcileParserResultVo, payConfig);
        log.info("[onlineReconcileTask] 解析对账文件输入流结果:{}", parserResult);
        return parserResult;
    }

    private ReconcileParserResultVo parserBillFileInputStream(InputStream inputStream, Date billDate,
                                                              ReconcileParserResultVo reconcileParserResultVo,
                                                              PayConfig payConfig) {
        ReconcileTaskBatch reconcileTaskBatch = reconcileParserResultVo.getReconcileTaskBatch();
        switch (payConfig.getChannelType()){
            case ALIPAY:{
                System.out.println("ALIPAY");
//                inputStream = AliPayBillFileDownStrategy.downloadFile(params);
                break;
            }
            case WECHAT:{
                log.info("WECHAT");
                return parserWeChatV2BillFile(inputStream, billDate, reconcileParserResultVo);
            }
            default:
                reconcileTaskBatch.setCheckFailMsg("不支持的渠道类型");
                throw new RuntimeException("不支持的渠道类型");
        }
        return reconcileParserResultVo;
    }

    private ReconcileParserResultVo parserWeChatV2BillFile(InputStream inputStream, Date billDate, ReconcileParserResultVo reconcileParserResultVo) {

        ReconcileTaskBatch reconcileTaskBatch = reconcileParserResultVo.getReconcileTaskBatch();
        BigDecimal multi100 = new BigDecimal(100);
        List<ChannelReconTradeVo> channelReconTradeVoList = new ArrayList<>();
        // 使用 InputStreamReader 和 BufferedReader 读取输入流
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
            String line;
            List<String> recordStrList = new ArrayList<>();
            while ((line = reader.readLine()) != null) {
                if (StringUtils.isEmpty(line)) {
                    continue;
                }
                recordStrList.add(line);
            }
            // 交易时间,公众账号ID,商户号,子商户号,设备号,微信订单号,商户订单号,用户标识,交易类型,交易状态,付款银行,货币种类,总金额,企业红包金额,商品名称,商户数据包,手续费,费率
            // `2016-02-16
            // 20:30:28,`wx3798432a27e0c92a,`1263453701,`1308363301,`,`1000010956201602163321502558,`PAY2016021610017753,`ozSK7wgGhLpgZ09x_OjkKgz0Zeis,`MICROPAY,`SUCCESS,`CFT,`CNY,`32.50,`0.00,`erp
            // product,`,`0.20000,`0.60%
            // `2016-02-16
            // 18:42:38,`wx3798432a27e0c92a,`1263453701,`1308363301,`,`1000020956201602163316153533,`PAY2016021610017685,`ozSK7wswIt3nBfUxsJDp5hWZSm_8,`MICROPAY,`SUCCESS,`CFT,`CNY,`95.00,`0.00,`erp
            // product,`,`0.57000,`0.60%

            // 总交易单数,总交易额,总退款金额,总企业红包退款金额,手续费总金额
            // `383,`32903.45,`0.00,`0.00,`197.45000

            String title = WeChatParamNameConstant.WECHAT_V2_BILL_FILE_TITLE_RAW_CONTENT;
            String totalTitle = WeChatParamNameConstant.WECHAT_V2_BILL_FILE_TOTAL_TITLE_RAW_CONTENT;
            // (.*?) 表示匹配任意字符（非贪婪模式），并用逗号 , 作为分隔符。$ 表示字符串的结束。
            Pattern titlePattern = Pattern.compile(WeChatParamNameConstant.WECHAT_V2_BILL_FILE_TITLE_RAW_CONTENT_REGEX);
            Pattern pattern = Pattern.compile(WeChatParamNameConstant.WECHAT_V2_BILL_FILE_TITLE_RAW_VALUE_REGEX);

            Pattern totalTitlePattern = Pattern.compile(WeChatParamNameConstant.WECHAT_V2_BILL_FILE_TOTAL_TITLE_RAW_CONTENT_REGEX);
            Pattern totalPattern = Pattern.compile(WeChatParamNameConstant.WECHAT_V2_BILL_FILE_TOTAL_TITLE_RAW_VALUE_REGEX);

            // 校验标题行
            String titleRawData = recordStrList.remove(0);
            if (!titlePattern.matcher(titleRawData).find()) {
               reconcileTaskBatch.setTaskBatchStatus(TaskBatchStatusEnum.FAIL);
                reconcileTaskBatch.setCheckFailMsg("校验标题行不通过, raw data[" + titleRawData + "], 期望值[" + title + "]");
                throw new RuntimeException();
            }
            // 解析统计数据
            // 这两行的顺序不能变
            String totalRawData = recordStrList.remove(recordStrList.size() - 1);
            String totalTitleRawData = recordStrList.remove(recordStrList.size() - 1);
            if (!totalTitlePattern.matcher(totalTitleRawData).find()) {
               reconcileTaskBatch.setTaskBatchStatus(TaskBatchStatusEnum.FAIL);
               reconcileTaskBatch.setCheckFailMsg("校验统计标题行不通过, raw data[" + totalTitleRawData + "], 期望值[" + totalTitle + "]");
                throw new RuntimeException();
            }

            Matcher totalMatcher = totalPattern.matcher(totalRawData);
            if (totalMatcher.find()) {
                // 总交易单数
                String totalCount = totalMatcher.group(1);
                // 应结订单总金额
                String totalAmountStr = totalMatcher.group(2);
                // 退款总金额
                String refundAmountStr = totalMatcher.group(3);
                // 充值券退款总金额
                String couponRefundAmountStr = totalMatcher.group(4);
                // 手续费总金额
                String feeAmountStr = totalMatcher.group(5);
                // 订单总金额
                String totalOrderAmountStr = totalMatcher.group(6);
                // 申请退款总金额
                String applyRefundAmountStr = totalMatcher.group(7);

                try {
                    reconcileTaskBatch.setTotalTradeCount(Integer.parseInt(totalCount));
                    // 微信账单金额单位是元
                    reconcileTaskBatch.setTotalTradeAmount(new BigDecimal(totalAmountStr).multiply(multi100).longValue());
                    reconcileTaskBatch.setTotalRefundAmount(new BigDecimal(refundAmountStr).multiply(multi100).longValue());
                    reconcileTaskBatch.setOutFee(new BigDecimal(feeAmountStr).multiply(multi100).longValue());
                } catch (NumberFormatException e) {
                    log.warn("解析统计行失败, billDate[" + billDate + "], billType[SUCCESS], raw data[" + totalRawData + "]", e);
                    reconcileTaskBatch.setTaskBatchStatus(TaskBatchStatusEnum.FAIL);
                    reconcileTaskBatch.setCheckFailMsg("解析统计行失败, raw data[" + totalRawData + "]");
                    // 恢复空值
                    reconcileTaskBatch.setTotalTradeCount(null);
                    reconcileTaskBatch.setTotalTradeAmount(null);
                    reconcileTaskBatch.setTotalRefundAmount(null);
                    reconcileTaskBatch.setOutFee(null);
                    throw  new RuntimeException();
                }
            } else {
                reconcileTaskBatch.setTaskBatchStatus(TaskBatchStatusEnum.FAIL);
                reconcileTaskBatch.setCheckFailMsg("匹配统计行失败, raw data[" + totalRawData + "]");
                throw new RuntimeException();
            }
            for (String rawData : recordStrList) {
                ChannelReconTradeVo channelReconTradeVo = new ChannelReconTradeVo();
                channelReconTradeVoList.add(channelReconTradeVo);
                Matcher matcher = pattern.matcher(rawData);
                if (matcher.find()) {

                    // 交易时间
                    String tradeTimeStr = matcher.group(1);
                    // 公众号ID
                    String appId = matcher.group(2);
                    // 商户号
                    String mchNo = matcher.group(3);
                    // 特约商户号
                    String subMchNo = matcher.group(4);
                    // 设备号
                    String deviceNo = matcher.group(5);
                    // 渠道订单号(微信流水号)
                    String outTradeOrderNo = matcher.group(6);
                    // 商户订单号(平台传递给微信的银行订单号)
                    String tradeOrderNo = matcher.group(7);
                    // 用户标识
                    String openId = matcher.group(8);
                    // 交易类型
                    String tradeType = matcher.group(9);
                    // 交易状态
                    String outTradeStatus = matcher.group(10);
                    // 付款银行
                    String bankPayType = matcher.group(11);
                    // 货币种类
                    String bankCurrency = matcher.group(12);
                    // 应结订单金额
                    String orderAmount = matcher.group(13);
                    // 代金券金额
                    String couponAmount = matcher.group(14);
                    // 微信退款单号
                    String refundOutTradeOrderNo = matcher.group(15);
                    // 商户退款单号
                    String refundTradeOrderNo = matcher.group(16);
                    // 退款金额
                    String refundAmount = matcher.group(17);
                    // 充值券退款金额
                    String couponRefundAmount = matcher.group(18);
                    // 退款类型
                    String refundType = matcher.group(19);
                    // 退款状态
                    String refundStatus = matcher.group(20);
                    // 商品名称
                    String goodsName = matcher.group(21);
                    // 商户数据包
                    String attach = matcher.group(22);
                    // 手续费
                    String fee = matcher.group(23);
                    // 费率
                    String feeRate = matcher.group(24);
                    // 订单金额
                    String totalAmount = matcher.group(25);
                    // 申请退款金额
                    String applyRefundAmount = matcher.group(26);
                    // 费率备注
                    String feeRemark = matcher.group(27);
                    // 设置支付时间
                    channelReconTradeVo.setTradeTime(DateFormatUtils.parse(tradeTimeStr));
                    // 设置微信流水号
                    channelReconTradeVo.setOutTradeOrderNo(outTradeOrderNo);
                    // 设置平台订单号
                    channelReconTradeVo.setTradeOrderNo(tradeOrderNo);
                    // 设置微信订单状态（默认全部是success）
                    channelReconTradeVo.setTradeStatus(TradeStatusEnum.valueOf(outTradeStatus));
                    // 设置微信账单金额:(单位是元)
                    channelReconTradeVo.setAmount(new BigDecimal(orderAmount).multiply(multi100).longValue());
                    //
                    channelReconTradeVo.setChannelFee(new BigDecimal(fee).multiply(multi100).longValue());
                } else {
                   reconcileTaskBatch.setTaskBatchStatus(TaskBatchStatusEnum.FAIL);
                   reconcileTaskBatch.setCheckFailMsg("匹配账单明细失败, raw data[" + rawData + "]");
                   throw new RuntimeException();
                }
            }
        } catch (Exception e) {
            log.info("Error reading input stream {}", e.getMessage());
            throw  new RuntimeException();
        }
        reconcileParserResultVo.setChannelReconTradeVoList(channelReconTradeVoList);
        reconcileParserResultVo.setReconcileTaskBatch(reconcileTaskBatch);
        return reconcileParserResultVo;
    }

    private InputStream getBillFileInputStream(Date billDate, PayConfig payConfig) {
        InputStream inputStream = null;
        HashMap<String, String> params = new HashMap<>();
        switch (payConfig.getChannelType()){
            case ALIPAY:{
                log.info("ALIPAY");
                AliPayConfig aliPayConfig = (AliPayConfig) payConfig;
                // 沙箱环境
                params.put(AliParamNameConstant.IS_SANDBOX, AliParamNameConstant.VALUE_FALSE);
                // 接口加签方式
                params.put(AliParamNameConstant.INTERFACE_SIGN_TYPE, AliParamNameConstant.INTERFACE_SIGN_TYPE_VALUE_SECRET);
                // 是否服务商模式
                params.put(AliParamNameConstant.IS_PARENT_SERVE, AliParamNameConstant.VALUE_FALSE);
                // appId
                params.put(AliParamNameConstant.APPID, aliPayConfig.getAppId());
                // 账单日期
                params.put(AliParamNameConstant.BILL_DATE, DateFormatUtils.format(billDate, DateFormatUtils.PATTERN_CLASSICAL_YMD));
                // 账单类型
                params.put(AliParamNameConstant.BILL_TYPE, AliParamNameConstant.BILL_TYPE_VALUE_ALL);
                // 账单压缩格式
                params.put(AliParamNameConstant.BILL_TAR_TYPE, AliParamNameConstant.BILL_TAR_TYPE_VALUE_GZIP);
                // 私钥
                params.put(AliParamNameConstant.PRIVATE_KEY, aliPayConfig.getPrivateKey());
                // 公钥
                params.put(AliParamNameConstant.PUBLIC_KEY, aliPayConfig.getPublicKey());
                // app证书路径
                params.put(AliParamNameConstant.APP_CERT_PATH, aliPayConfig.getAppCertPath());
                // app证书内容
                params.put(AliParamNameConstant.APP_CERT_B64, aliPayConfig.getAppCertB64());
                // 支付宝公钥证书路径
                params.put(AliParamNameConstant.ALIPAY_CERT_PATH, aliPayConfig.getAlipayCertPath());
                params.put(AliParamNameConstant.ALIPAY_CERT_B64, aliPayConfig.getAlipayCertB64());
                // 根证书路径
                params.put(AliParamNameConstant.ALIPAY_ROOT_CERT_PATH, aliPayConfig.getAlipayRootCertPath());
                // 根证书内容
                params.put(AliParamNameConstant.ALIPAY_ROOT_CERT_B64, aliPayConfig.getAlipayRootCertB64());
                // 密钥授权token
                params.put(AliParamNameConstant.APP_AUTH_TOKEN, aliPayConfig.getAppAuthToken());
                inputStream = aliPayBillFileDownStrategy.getBillFileInputStream(params);
                break;
            }
            case WECHAT:{
                WechatPayConfig wechatPayConfig = (WechatPayConfig) payConfig;
                log.info("WECHAT_DIRECT");
                params.put(WeChatParamNameConstant.IS_SANDBOX,WeChatParamNameConstant.VALUE_FALSE);

                params.put(WeChatParamNameConstant.APPID,wechatPayConfig.getAppId());
                params.put(WeChatParamNameConstant.MCH_ID,wechatPayConfig.getMchNo());
                params.put(WeChatParamNameConstant.PARTNER_KEY,wechatPayConfig.getPartnerKey());

                params.put(WeChatParamNameConstant.BILL_DATE, DateFormatUtils.format(billDate, DateFormatUtils.PATTERN_CLASSICAL_YMD));
                params.put(WeChatParamNameConstant.BILL_TYPE, WeChatParamNameConstant.BILL_TYPE_VALUE_ALL);
                params.put(WeChatParamNameConstant.BILL_TAR_TYPE, WeChatParamNameConstant.BILL_TAR_TYPE_VALUE_GZIP);
                inputStream = wechatBillFileDownStrategy.getBillFileInputStream(params);
                break;
            }
            default:
                throw new RuntimeException("不支持的渠道类型");
        }
        return inputStream;
    }

    private void check(ReconcileParserResultVo reconcileParserResultVo) {

        List<ChannelReconTradeVo> channelReconTradeVoList = reconcileParserResultVo.getChannelReconTradeVoList();
        ReconcileTaskBatch reconcileTaskBatch = reconcileParserResultVo.getReconcileTaskBatch();

        reconcileTaskBatch.setReconcileStartTime(new Date());
        // @TODO 使用hutool校验list数据 , 就算账单没有数据，也可能有平台的订单流水，不能跳出
        if (CollUtil.isEmpty(channelReconTradeVoList)){
            reconcileTaskBatch.setTaskBatchStatus(TaskBatchStatusEnum.NO_BILL);
        }

        reconcileTaskBatch.setReconcileStartTime(new Date());

        Date billDate = reconcileTaskBatch.getBillDate();
        Date payStartTime = DateFormatUtils.getStartTime(billDate);
        Date payEndTime = DateFormatUtils.getEndTime(billDate);

        List<PlatformOrder> platformOrderSuccessList =
                platformOrderService.findByOrderStatusList(
                        Collections.singletonList(OrderState.SUCCESS.getCode()),
                        payStartTime,
                        payEndTime
                );
        log.info("[reconciliationHandler] 获取平台对账日期内【交易成功状态】的订单流水数据 platformOrderSuccessList = {}",platformOrderSuccessList.size());
        List<PlatformOrder> platformOrderAllList =
                platformOrderService.findByOrderStatusList(
                        CollUtil.newArrayList(),
                        payStartTime,
                        payEndTime
                );
        log.info("[reconciliationHandler] 获取平台对账日期内【所有交易状态】的订单流水数据，即平台的所有订单流水数据，不管否交易成功 platformOrderAllList = {}",platformOrderAllList.size());
        List<ReconcileMistakeScratchPool> platScratchRecordList = reconcileMistakeScratchPoolService.listScratchPoolRecord(payStartTime,payEndTime);
        log.info("[reconciliationHandler] 查询平台缓冲池中所有的数据 platScratchRecordList = {}",platScratchRecordList.size());

        // 差错list
        List<ReconcileMistake> mistakeList = new ArrayList<>();

        // 需要放入缓冲池中平台长款list
        List<ReconcileMistakeScratchPool> insertScreatchRecordList = new ArrayList<>();

        // 需要从缓冲池中移除的数据
        List<ReconcileMistakeScratchPool> removeScreatchRecordList = new ArrayList<>();

        // TODO 基于平台订单流水和渠道订单流水做对比
        log.info("[reconciliationHandler] 开始以平台的数据为准对账,平台长款记入缓冲池");
        baseOnPlatform(platformOrderSuccessList, channelReconTradeVoList, mistakeList, insertScreatchRecordList, reconcileTaskBatch);
        log.info("[reconciliationHandler] 结束以平台的数据为准对账");
        // TODO 基于渠道订单流水和平台订单流水做对比
        log.info("[reconciliationHandler] 开始以银行通道的数据为准对账");
        baseOnChannel(platformOrderAllList, channelReconTradeVoList, platScratchRecordList, mistakeList, reconcileTaskBatch, removeScreatchRecordList);
        log.info("[reconciliationHandler] 结束以银行通道的数据为准对账");

        reconcileParserResultVo.setMistakeList(mistakeList);
        reconcileParserResultVo.setInsertScreatchRecordList(insertScreatchRecordList);
        reconcileParserResultVo.setRemoveScreatchRecordList(removeScreatchRecordList);
        reconcileParserResultVo.setReconcileTaskBatch(reconcileTaskBatch);

    }

    private void baseOnChannel(List<PlatformOrder> platformOrderAllList,
                               List<ChannelReconTradeVo> channelReconTradeVoList,
                               List<ReconcileMistakeScratchPool> platScreatchRecordList,
                               List<ReconcileMistake> mistakeList,
                               ReconcileTaskBatch reconcileTaskBatch,
                               List<ReconcileMistakeScratchPool> removeScreatchRecordList) {

        // 平台交易总金额
        Long platformTotalTradeAmount = 0L;
        // 平台总手续费
        Long platformTotalFee = 0L;
        // 平台订单总数
        int platformTotalTradeCount = 0;
        // 差错总数
        int platformTotalMistakeCount = 0;

        for (ChannelReconTradeVo channelRecord : channelReconTradeVoList){
            // 用于标记是否有匹配
            boolean flag = false;
            for (PlatformOrder record : platformOrderAllList){
                /** step1 检查有匹配上的数据 **/
                if (ObjectUtil.equals(record.getOutTradeOrderNo(), channelRecord.getOutTradeOrderNo())){
                    flag = true;
                    /** step2： 判断平台【订单流水交易状态】是否与【渠道交易状态】匹配 **/
                    /** 注意：在状态匹配的情况下不需要重复做金额和手续费的验证，因为以平台数据为基准对账已经做了验证 **/
                    // 渠道中有数据就表示已经交易成功了，这里看平台的交易状态，如果不匹配则记录差错。
                    if (!ObjectUtil.equals(OrderState.SUCCESS.getName(), record.getTradeOrderNo())){
                        ReconcileMistake mistakeRecord =
                                createMistakeRecord(null, record, channelRecord, ReconcileMistakeType.PLATFORM_SHORT_STATUS_MISMATCH, reconcileTaskBatch);
                        mistakeList.add(mistakeRecord);
                        platformTotalMistakeCount++;


                // 状态的差错记录完，再检查交易状态不匹配的这笔订单流水的金额和手续费是否匹配，如果不匹配
                // 同样需要给这笔订单流水记录金额不匹配的差错记录以及手续费不匹配的差错记录，在差错处理的时候必须先处理状态不符的情况
                        // 平台流水金额大于渠道流水金额
                        if (record.getPayAmount() > channelRecord.getAmount()){
                            ReconcileMistake mistakeRecord2 =
                                    createMistakeRecord(null, record, channelRecord, ReconcileMistakeType.PLATFORM_OVER_CASH_MISMATCH, reconcileTaskBatch);
                            mistakeList.add(mistakeRecord2);
                            platformTotalMistakeCount++;
                            break;
                        }
                        // 平台流水金额小于渠道流水金额
                        else if (record.getPayAmount() < channelRecord.getAmount()){
                            ReconcileMistake mistakeRecord2 =
                                    createMistakeRecord(null, record, channelRecord, ReconcileMistakeType.PLATFORM_SHORT_CASH_MISMATCH, reconcileTaskBatch);
                            mistakeList.add(mistakeRecord2);
                            platformTotalMistakeCount++;
                            break;
                        }

                        // 匹配订单手续费
                        if (!ObjectUtil.equals(record.getFee(), channelRecord.getChannelFee())){

                            ReconcileMistake mistakeRecord2 =
                                    createMistakeRecord(null, record, channelRecord, ReconcileMistakeType.FEE_MISMATCH, reconcileTaskBatch);
                            mistakeList.add(mistakeRecord2);
                            platformTotalMistakeCount++;
                            break;
                        }
                    }
                }
            }
            /** step3： 如果没有匹配的数据，去缓冲池中查找对账，如果没有记录差错 **/
            if (!flag){
                // 去缓冲池中查找对账(前提是缓冲池里面有数据)
                if (!CollectionUtil.isEmpty(platScreatchRecordList)){
                    for (ReconcileMistakeScratchPool scratchPoolRecord : platScreatchRecordList){
                        if (ObjectUtil.equals(scratchPoolRecord.getTradeOrderNo(), channelRecord.getOutTradeOrderNo())){
                            // 累计平台交易总金额和总手续费
                            platformTotalTradeAmount += scratchPoolRecord.getTradeOrderAmount();
                            platformTotalFee += scratchPoolRecord.getFee();
                            platformTotalTradeCount++;
                            flag = true;
                            // 验证金额和手续费
                            // 平台流水金额大于渠道流水金额
                            if (scratchPoolRecord.getTradeOrderAmount() > channelRecord.getAmount()){
                                ReconcileMistake mistakeRecord =
                                        createMistakeRecord(scratchPoolRecord, null, channelRecord, ReconcileMistakeType.PLATFORM_OVER_CASH_MISMATCH, reconcileTaskBatch);
                                mistakeList.add(mistakeRecord);
                                platformTotalMistakeCount++;
                                break;
                            }
                            // 平台流水金额小于渠道流水金额
                            else if (scratchPoolRecord.getTradeOrderAmount() < channelRecord.getAmount()){
                                ReconcileMistake mistakeRecord =
                                        createMistakeRecord(scratchPoolRecord, null, channelRecord, ReconcileMistakeType.PLATFORM_SHORT_CASH_MISMATCH, reconcileTaskBatch);
                                mistakeList.add(mistakeRecord);
                                platformTotalMistakeCount++;
                                break;
                            }

                            // 匹配订单手续费
                            if (!ObjectUtil.equals(scratchPoolRecord.getFee(), channelRecord.getChannelFee())){
                                ReconcileMistake mistakeRecord =
                                        createMistakeRecord(scratchPoolRecord, null, channelRecord, ReconcileMistakeType.FEE_MISMATCH, reconcileTaskBatch);
                                mistakeList.add(mistakeRecord);
                                platformTotalMistakeCount++;
                                break;
                            }
                            // 匹配上的订单需要删除缓冲池中的记录
                            removeScreatchRecordList.add(scratchPoolRecord);
                        }
                    }
                }

            }

            // 缓冲池中还是没有这条记录,直接记录差错，差错类型为 PLATFORM_MISS("平台漏单")
            if (!flag) {
                ReconcileMistake mistakeRecord = createMistakeRecord(null, null, channelRecord, ReconcileMistakeType.PLATFORM_MISS, reconcileTaskBatch);
                mistakeList.add(mistakeRecord);
                platformTotalMistakeCount++;
            }
        }
        // 保存统计数据
        reconcileTaskBatch.setTotalTradeAmount(reconcileTaskBatch.getTotalOutTradeAmount() + platformTotalTradeAmount);
        reconcileTaskBatch.setTotalTradeCount(reconcileTaskBatch.getTotalTradeCount() + platformTotalTradeCount);
        reconcileTaskBatch.setFee(reconcileTaskBatch.getFee() + platformTotalFee);
        reconcileTaskBatch.setTotalMistakeCount(reconcileTaskBatch.getTotalMistakeCount() + platformTotalMistakeCount);

    }

    private void baseOnPlatform(List<PlatformOrder> platformOrderSucessList,
                                List<ChannelReconTradeVo> channelReconTradeVoList,
                                List<ReconcileMistake> mistakeList,
                                List<ReconcileMistakeScratchPool> screatchRecordList,
                                ReconcileTaskBatch reconcileTaskBatch) {

        // 平台交易总金额
        Long platformTotalTradeAmount = 0L;
        // 平台总手续费
        Long platformTotalFee = 0L;
        // 平台订单总数
        int platformTotalTradeCount = 0;
        // 差错总数
        int platformTotalMistakeCount = 0;

        for (PlatformOrder record : platformOrderSucessList) {
            // 累计平台总数据
            platformTotalTradeAmount += record.getPayAmount();
            // 累计平台总手续费
            platformTotalFee += record.getFee();
            // 平台订单总数
            platformTotalTradeCount++;
            boolean flag = false;// 用于标记是否有匹配
            for (ChannelReconTradeVo channelRecord : channelReconTradeVoList){
                // 匹配上数据
                if (ObjectUtil.equals(record.getOutTradeOrderNo(), channelRecord.getOutTradeOrderNo())){
                    flag = true;

                    /* 匹配订单金额 */
                    // 平台金额比渠道订单金额大，则认为平台长款，把平台订单记录到缓冲池中
                    if (record.getPayAmount() > channelRecord.getAmount()){
                        // 金额不匹配，创建差错记录
                        ReconcileMistake mistake = createMistakeRecord(null, record, channelRecord, ReconcileMistakeType.PLATFORM_OVER_CASH_MISMATCH, reconcileTaskBatch);
                        mistakeList.add(mistake);
                        platformTotalMistakeCount++;
                        break;
                    }
                    // 平台金额比渠道订单金额小，则认为渠道长款，把渠道订单记录到缓冲池中
                    else if (record.getPayAmount() < channelRecord.getAmount()){
                        // 金额不匹配，创建差错记录
                        ReconcileMistake mistake = createMistakeRecord(null, record, channelRecord, ReconcileMistakeType.PLATFORM_SHORT_CASH_MISMATCH, reconcileTaskBatch);
                        mistakeList.add(mistake);
                        platformTotalMistakeCount++;
                        break;
                    }
                    else {
                        // 金额匹配，不记录差错
                        log.info("平台订单金额和银行账单金额一致，不记录差错");
                    }
                    /* step2:匹配订单手续费 **/
                    if (ObjectUtil.equals(record.getFee(), channelRecord.getChannelFee())) {
                        // 金额不匹配，创建差错记录
                        ReconcileMistake mistake = createMistakeRecord(null, record, channelRecord, ReconcileMistakeType.FEE_MISMATCH, reconcileTaskBatch);
                        mistakeList.add(mistake);
                        platformTotalMistakeCount++;
                        break;
                    }
                }
            }
            // 没有找到匹配的记录，把这个订单记录到缓冲池中
            if (!flag) {
                ReconcileMistakeScratchPool scratchRecord = createScratchRecord(record, reconcileTaskBatch);
                screatchRecordList.add(scratchRecord);
            }
        }
        // 统计数据保存
        // 平台交易总金额
        reconcileTaskBatch.setTotalTradeAmount(platformTotalTradeAmount);
        // 平台订单总数
        reconcileTaskBatch.setTotalTradeCount(platformTotalTradeCount);
        // 平台总手续费
        reconcileTaskBatch.setFee(platformTotalFee);
        // 差错总数
        reconcileTaskBatch.setTotalMistakeCount(platformTotalMistakeCount);
    }

    /**
     * 得到缓存记录：用于放入缓冲池
     * @param record
     *            平台流水支付记录
     * @param reconcileTaskBatch
     *            对账批次记录
     * @return ReconcileMistakeScratchPool
     */
    private ReconcileMistakeScratchPool createScratchRecord(PlatformOrder record, ReconcileTaskBatch reconcileTaskBatch) {
        ReconcileMistakeScratchPool scratchRecord = new ReconcileMistakeScratchPool();
        scratchRecord.setOutTradeOrderNo(String.valueOf(record.getOutTradeOrderNo()));
        scratchRecord.setTradeOrderPayTime(record.getTradeOrderPayTime());
        scratchRecord.setTradeOrderNo(record.getTradeOrderNo());
        scratchRecord.setTradeOrderAmount(record.getPayAmount());
        scratchRecord.setTradeOrderStatus(OrderState.SUCCESS);
        scratchRecord.setTaskBatchNo(reconcileTaskBatch.getTaskBatchNo());
        scratchRecord.setBillDate(reconcileTaskBatch.getBillDate());
        return scratchRecord;
    }

    /**
     * 创建差错记录
     *
     * @param scratchPoolRecord
     *            平台缓冲池中的订单记录
     * @param record
     *            平台订单记录
     * @param channelRecord
     *            银行账单记录
     * @param mistakeType
     *            差错类型
     * @return 注意：scratchRecord和record 至少有一个为空
     */
    private ReconcileMistake createMistakeRecord(
            ReconcileMistakeScratchPool scratchPoolRecord,
            PlatformOrder record, ChannelReconTradeVo channelRecord,
            ReconcileMistakeType mistakeType,
            ReconcileTaskBatch reconcileTaskBatch) {

        ReconcileMistake mistake = new ReconcileMistake();
        mistake.setTaskBatchNo(reconcileTaskBatch.getTaskBatchNo());
        mistake.setBillDate(reconcileTaskBatch.getBillDate());
        mistake.setMistakeType(mistakeType);
        mistake.setHandlerType(HandlerType.NO_HANDLE);
        mistake.setChannelType(reconcileTaskBatch.getChannelType());

        // 平台订单信息
        if (record != null) {
            mistake.setMchName(reconcileTaskBatch.getMchName());
            mistake.setMchNo(reconcileTaskBatch.getMchNo());
            mistake.setTradeOrderNo(record.getTradeOrderNo());
            mistake.setTradeOrderPayTime(record.getTradeOrderPayTime());
            mistake.setOutTradeOrderNo(String.valueOf(record.getOutTradeOrderNo()));
            mistake.setTradeOrderAmount(record.getPayAmount());
            mistake.setRefundAmount(record.getSuccessRefundAmount());
            mistake.setTradeOrderStatus(record.getTradeOrderStatus());
            mistake.setFee(record.getFee());
        }

        // 缓冲池中的订单信息
        if (scratchPoolRecord != null) {
            mistake.setTradeOrderNo(scratchPoolRecord.getTradeOrderNo());
            mistake.setTradeOrderPayTime(scratchPoolRecord.getTradeOrderPayTime());
            mistake.setOutTradeOrderNo(scratchPoolRecord.getOutTradeOrderNo());
            mistake.setTradeOrderAmount(scratchPoolRecord.getTradeOrderAmount());
            mistake.setRefundAmount(scratchPoolRecord.getRefundAmount());
            mistake.setTradeOrderStatus(scratchPoolRecord.getTradeOrderStatus());
            mistake.setFee(scratchPoolRecord.getFee());
        }

        if (channelRecord != null) {
            mistake.setTradeOrderAmount(channelRecord.getAmount());
            mistake.setFee(channelRecord.getChannelFee());
            mistake.setOutTradeOrderNo(channelRecord.getOutTradeOrderNo());
            mistake.setRefundAmount(channelRecord.getRefundAmount());
            mistake.setOutTradeOrderStatus(channelRecord.getTradeStatus().getName());
            mistake.setTradeOrderPayTime(channelRecord.getTradeTime());
            mistake.setOutTradeOrderNo(channelRecord.getOutTradeOrderNo());
        }
        return mistake;

    }

    private void validateScratchPool() {

        QueryWrapper<ReconcileMistakeScratchPool> queryWrapper = new QueryWrapper<>();
        // 查询三天前创建的差错缓冲记录
        queryWrapper.lt("create_time", DateUtil.offsetDay(new Date(), -3));

        List<ReconcileMistakeScratchPool> scratchPoolList = reconcileMistakeScratchPoolService.list(queryWrapper);
        List<ReconcileMistake> mistakeList = new ArrayList<>();

        // 如果有三天前的缓冲记录，则直接记录差错
        if (CollUtil.isNotEmpty(scratchPoolList)){
            scratchPoolList.forEach(scratchPoolRecord -> {
                ReconcileMistake reconcileMistake = new ReconcileMistake();
                reconcileMistake.setTaskBatchNo(scratchPoolRecord.getTaskBatchNo());
                reconcileMistake.setBillDate(scratchPoolRecord.getBillDate());
                reconcileMistake.setMistakeType(ReconcileMistakeType.CHANNEL_MISS);
                reconcileMistake.setHandlerType(HandlerType.NO_HANDLE);
                reconcileMistake.setChannelType(scratchPoolRecord.getChannelType());
                reconcileMistake.setTradeOrderNo(scratchPoolRecord.getTradeOrderNo());
                reconcileMistake.setTradeOrderPayTime(scratchPoolRecord.getTradeOrderPayTime());
                reconcileMistake.setOutTradeOrderNo(scratchPoolRecord.getOutTradeOrderNo());
                reconcileMistake.setTradeOrderAmount(scratchPoolRecord.getTradeOrderAmount());
                reconcileMistake.setRefundAmount(scratchPoolRecord.getRefundAmount());
                reconcileMistake.setTradeOrderStatus(scratchPoolRecord.getTradeOrderStatus());
                reconcileMistake.setFee(scratchPoolRecord.getFee());
                mistakeList.add(reconcileMistake);
            });
            log.info("========>  清理缓冲池中没有对账的数据，记录差错=========>");

            log.info("===> step1:保存差错记录====总共[" + mistakeList.size() + "]条");
            reconcileMistakeService.saveBatch(mistakeList);

            log.info("===> step2:从缓存池中删除已匹配记录====总共[" + scratchPoolList.size() + "]条");
            reconcileMistakeScratchPoolService.deleteFromPool(scratchPoolList);

            log.info("<========  清理缓冲池中没有对账的数据，记录差错结束<=========");
        }
    }


    /**
     * 使用反射设置对象的属性值。
     *
     * @param obj   要设置属性值的对象
     * @param field 属性
     * @param value 属性值
     * @throws IllegalAccessException 如果没有足够的权限访问属性
     */
    public static void setFieldValue(Object obj, Field field, String value)
            throws IllegalAccessException, IntrospectionException, InvocationTargetException, ParseException {

        Class<?> aClass = obj.getClass();
        PropertyDescriptor propertyDescriptor = new PropertyDescriptor(field.getName(), aClass);
        Method method = propertyDescriptor.getWriteMethod();
        String type = field.getGenericType()
                .toString();
        if (!org.springframework.util.StringUtils.hasText(value)) {
            return;
        }
        value = value.trim();
        switch (type) {
            case "class java.util.Date":
                method.invoke(obj, DateUtils.parseDate(value,"yyyy-MM-dd HH:mm:ss"));
                break;
            case "class java.lang.String":
                method.invoke(obj, value);
                break;
            case "class java.lang.Integer":
                method.invoke(obj, Double.valueOf(value)
                        .intValue());
                break;
            case "class java.lang.Long":
                method.invoke(obj, Double.valueOf(value)
                        .longValue());
                break;
            case "class java.lang.Double":
                method.invoke(obj, Double.valueOf(value));
                break;
            case "class java.lang.Float":
                method.invoke(obj, Double.valueOf(value)
                        .floatValue());
                break;
            case "class java.lang.Character":
                method.invoke(obj, value.toCharArray()[0]);
                break;
            case "class java.math.BigDecimal":
                method.invoke(obj, new BigDecimal(value).setScale(4, RoundingMode.HALF_UP));
                break;
            default:
                method.invoke(obj, (Object) null);
                break;
        }
    }
}
